package ca.uhn.fhir.tinder;

import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.tinder.AbstractGenerator.FailureException;
import ca.uhn.fhir.tinder.GeneratorContext.ResourceSource;
import ca.uhn.fhir.tinder.TinderStructuresMojo.ValueSetFileDefinition;
import ca.uhn.fhir.tinder.parser.BaseStructureParser;
import ca.uhn.fhir.tinder.parser.DatatypeGeneratorUsingSpreadsheet;
import ca.uhn.fhir.tinder.parser.TargetType;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Generate files from FHIR resource/composite metadata using Velocity templates.
 * <p>
 * Generates either source or resource files for each selected resource or
 * composite data type. One file is generated for each selected entity. The
 * files are generated using a Velocity template that can be taken from
 * inside the hapi-timder-plugin project or can be located in other projects
 * <p>
 * The following Maven plug-in configuration properties are used with this plug-in
 * </p>
 *
 * <table border="1">
 *    <caption>Configuration Properties</caption>
 *   <tr>
 *     <td><b>Attribute</b></td>
 *     <td><b>Description</b></td>
 *     <td><b>Required</b></td>
 *   </tr>
 *   <tr>
 *     <td>version</td>
 *     <td>The FHIR version whose resource metadata
 *     is to be used to generate the files<br>
 *     Valid values:&nbsp;<code><b>dstu2</b></code>&nbsp;|&nbsp;<code><b>dstu3</b></code>&nbsp;|&nbsp;<code><b>r4</b></code>&nbsp;|&nbsp;<code><b>r5</b></code></td>
 *     <td>Yes</td>
 *   </tr>
 *   <tr>
 *     <td>baseDir</td>
 *     <td>The Maven project's base directory. This is used to
 *     possibly locate other assets within the project used in file generation.</td>
 *     <td>No. Defaults to: <code>${project.build.directory}/..</code></td>
 *   </tr>
 *   <tr>
 *     <td>generateResources</td>
 *     <td>Should files be generated from FHIR resource metadata?<br>
 *     Valid values:&nbsp;<code><b>true</b></code>&nbsp;|&nbsp;<code><b>false</b></code></td>
 *     <td rowspan="4">One of these four options must be specified as <code><b>true</b></code></td>
 *   </tr>
 *   <tr>
 *     <td>generateDataTypes</td>
 *     <td>Should files be generated from FHIR composite data type metadata?<br>
 *     Valid values:&nbsp;<code><b>true</b></code>&nbsp;|&nbsp;<code><b>false</b></code></td>
 *   </tr>
 *   <tr>
 *     <td>generateValueSets</td>
 *     <td>Should files be generated from FHIR value set metadata?<br>
 *     Valid values:&nbsp;<code><b>true</b></code>&nbsp;|&nbsp;<code><b>false</b></code></td>
 *   </tr>
 *   <tr>
 *     <td>generateProfiles</td>
 *     <td>Should files be generated from FHIR profile metadata?<br>
 *     Valid values:&nbsp;<code><b>true</b></code>&nbsp;|&nbsp;<code><b>false</b></code></td>
 *   </tr>
 *   <tr>
 *     <td>resourceSource</td>
 *     <td>Which source of resource definitions should be processed? Valid values are:<br>
 *     <ul>
 *     <li><code><b>spreadsheet</b></code>&nbsp;&nbsp;to cause resources to be generated based on the FHIR spreadsheets</li>
 *     <li><code><b>model</b></code>&nbsp;&nbsp;to cause resources to be generated based on the model structure classes. Note that
 *     <code>generateResources</code> is the only one of the above options that can be used when <code>model</code> is specified.</li></ul></td>
 *     <td>No. Defaults to: <code><b>spreadsheet</b></code></td>
 *   </tr>
 *   <tr>
 *     <td colspan="3"></td>
 *   </tr>
 *   <tr>
 *     <td colspan="3">Java source files can be generated
 *     for FHIR resources or composite data types. There is one file
 *     generated for each selected entity. The following configuration
 *     properties control the naming of the generated source files:<br>
 *     &nbsp;&nbsp;&nbsp;&nbsp;&lt;targetSourceDirectory&gt;/&lt;targetPackage&gt;/&lt;filenamePrefix&gt;<i>element-name</i>&lt;filenameSuffix&gt;<br>
 *     where: <i>element-name</i> is the "title-case" name of the selected resource or composite data type.<br>
 *     Note that all dots in the targetPackage will be replaced by the path separator character when building the
 *     actual source file location. Also note that <code>.java</code> will be added to the filenameSuffix if it is not already included.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>targetSourceDirectory</td>
 *     <td>The Maven source directory to contain the generated files.</td>
 *     <td>Yes when Java source files are to be generated</td>
 *   </tr>
 *   <tr>
 *     <td>targetPackage</td>
 *     <td>The Java package that will contain the generated classes.
 *     This package is generated in the &lt;targetSourceDirectory&gt; if needed.</td>
 *     <td>Yes when <i>targetSourceDirectory</i> is specified</td>
 *   </tr>
 *   <tr>
 *     <td>filenamePrefix</td>
 *     <td>The prefix string that is to be added onto the
 *     beginning of the resource or composite data type name to become
 *     the Java class name or resource file name.</td>
 *     <td>No</td>
 *   </tr>
 *   <tr>
 *     <td>filenameSuffix</td>
 *     <td>Suffix that will be added onto the end of the resource
 *     or composite data type name to become the Java class name or resource file name.</td>
 *     <td>No</td>
 *   </tr>
 *   <tr>
 *     <td colspan="3"></td>
 *   </tr>
 *   <tr>
 *     <td colspan="3">Maven resource files can also be generated
 *     for FHIR resources or composite data types. The following configuration
 *     properties control the naming of the generated resource files:<br>
 *     &nbsp;&nbsp;&nbsp;&nbsp;&lt;targetResourceDirectory&gt;/&lt;targetFolder&gt;/&lt;filenamePrefix&gt;<i>element-name</i>&lt;filenameSuffix&gt;<br>
 *     where: <i>element-name</i> is the "title-case" name of the selected resource or composite data type.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td>targetResourceDirectory</td>
 *     <td>The Maven resource directory to contain the generated files.</td>
 *     <td>Yes when resource files are to be generated</td>
 *   </tr>
 *   <tr>
 *     <td>targetFolder</td>
 *     <td>The folder within the targetResourceDirectory where the generated files will be placed.
 *     This folder is generated in the &lt;targetResourceDirectory&gt; if needed.</td>
 *     <td>No</td>
 *   </tr>
 *   <tr>
 *     <td colspan="3"></td>
 *   </tr>
 *   <tr>
 *     <td>template</td>
 *     <td>The path of one of the <i>Velocity</i> templates
 *     contained within the <code>hapi-tinder-plugin</code> Maven plug-in
 *     classpath that will be used to generate the files.</td>
 *     <td rowspan="2">One of these two options must be configured</td>
 *   </tr>
 *   <tr>
 *     <td>templateFile</td>
 *     <td>The full path to the <i>Velocity</i> template that is
 *     to be used to generate the files.</td>
 *   </tr>
 *   <tr>
 *     <td>velocityPath</td>
 *     <td>When using the <code>templateFile</code> option, this property
 *     can be used to specify where Velocity macros and other resources are located.</td>
 *     <td>No. Defaults to same directory as the template file.</td>
 *   </tr>
 *   <tr>
 *     <td>velocityProperties</td>
 *     <td>Specifies the full path to a java properties file
 *     containing Velocity configuration properties</td>
 *     <td>No.</td>
 *   </tr>
 *   <tr>
 *     <td>includeResources</td>
 *     <td>A list of the names of the resources or composite data types that should
 *     be used in the file generation</td>
 *     <td>No. Defaults to all defined resources except for DSTU2,
 *     the <code>Binary</code> resource is excluded and
 *     for DSTU3, the <code>Conformance</code> resource is excluded.</td>
 *   </tr>
 *   <tr>
 *     <td>excludeResources</td>
 *     <td>A list of the names of the resources or composite data types that should
 *     excluded from the file generation</td>
 *     <td>No.</td>
 *   </tr>
 *   <tr>
 *     <td>valueSetFiles</td>
 *     <td>A list of files containing value-set resource definitions
 *     to be used.</td>
 *     <td>No. Defaults to all defined value-sets that
 *     are referenced from the selected resources.</td>
 *   </tr>
 *   <tr>
 *     <td>profileFiles</td>
 *     <td>A list of files containing profile definitions
 *     to be used.</td>
 *     <td>No. Defaults to the default profile
 *     for each selected resource</td>
 *   </tr>
 * </table>
 *
 *
 *
 * @author Bill.Denton
 *
 */
@Mojo(name = "generate-multi-files", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
class TinderGenericMultiFileMojo extends AbstractMojo {

	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TinderGenericMultiFileMojo.class);

	@Parameter(required = true)
	private String version;

	@Parameter(required = true, defaultValue = "${project.build.directory}/..")
	private String baseDir;

	@Parameter(required = false, defaultValue = "false")
	private boolean generateResources;

	@Parameter(required = false, defaultValue = "false")
	private boolean generateDatatypes;

	@Parameter(required = false, defaultValue = "false")
	private boolean generateValueSets;

	@Parameter(required = false)
	private File targetSourceDirectory;

	@Parameter(required = false)
	private String targetPackage;

	@Parameter(required = false)
	private String filenamePrefix;

	@Parameter(required = false)
	private String filenameSuffix;

	@Parameter(required = false)
	private File targetResourceDirectory;

	@Parameter(required = false)
	private String targetFolder;

	// one of these two is required
	@Parameter(required = false)
	private String template;

	@Parameter(required = false)
	private File templateFile;

	@Parameter(required = false)
	private String velocityPath;

	@Parameter(required = false)
	private String velocityProperties;

	@Parameter(required = false)
	private List<String> includeResources;

	@Parameter(required = false)
	private List<String> excludeResources;

	@Parameter(required = false)
	private String resourceSource;

	@Parameter(required = false)
	private List<ValueSetFileDefinition> valueSetFiles;

	@Component
	private MavenProject myProject;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {

		GeneratorContext context = new GeneratorContext();
		Generator generator = new Generator();
		try {
			context.setVersion(version);
			context.setBaseDir(baseDir);
			context.setIncludeResources(includeResources);
			context.setExcludeResources(excludeResources);
			context.setResourceSource(resourceSource);
			context.setValueSetFiles(valueSetFiles);
			if (ResourceSource.MODEL.equals(context.getResourceSource())) {
				if (generateDatatypes) {
					throw new MojoFailureException(
							Msg.code(128) + "Cannot use \"generateDatatypes\" when resourceSource=model");
				}
				if (generateValueSets) {
					throw new MojoFailureException(
							Msg.code(129) + "Cannot use \"generateValueSets\" when resourceSource=model");
				}
			}

			generator.prepare(context);
		} catch (FailureException e) {
			throw new MojoFailureException(Msg.code(130) + e.getMessage(), e.getCause());
		}

		/*
		 * Deal with the generation target
		 */
		TargetType targetType = null;
		File targetDirectory = null;
		if (targetSourceDirectory != null) {
			if (targetResourceDirectory != null) {
				throw new MojoFailureException(
						Msg.code(131)
								+ "Both [targetSourceDirectory] and [targetResourceDirectory] are specified. Please choose just one.");
			}
			targetType = TargetType.SOURCE;
			if (null == targetPackage) {
				throw new MojoFailureException(Msg.code(132)
						+ "The [targetPackage] property must be specified when generating Java source code.");
			}
			targetDirectory = new File(targetSourceDirectory, targetPackage.replace('.', File.separatorChar));
		} else if (targetResourceDirectory != null) {
			if (targetSourceDirectory != null) {
				throw new MojoFailureException(
						Msg.code(133)
								+ "Both [targetSourceDirectory] and [targetResourceDirectory] are specified. Please choose just one.");
			}
			targetType = TargetType.RESOURCE;
			if (targetFolder != null) {
				targetDirectory = new File(targetResourceDirectory, targetFolder);
			} else {
				targetDirectory = targetResourceDirectory;
			}
			if (null == targetPackage) {
				targetPackage = "";
			}
		} else {
			throw new MojoFailureException(
					Msg.code(134) + "Either [targetSourceDirectory] or [targetResourceDirectory] must be specified.");
		}
		targetDirectory.mkdirs();
		ourLog.info(" * Output [" + targetType.toString() + "] Directory: " + targetDirectory.getAbsolutePath());

		/*
		 * Write resources if selected
		 */
		BaseStructureParser rp = context.getResourceGenerator();
		if (generateResources && rp != null) {
			ourLog.info("Writing Resources...");
			rp.setFilenamePrefix(filenamePrefix);
			rp.setFilenameSuffix(filenameSuffix);
			rp.setTemplate(template);
			rp.setTemplateFile(templateFile);
			rp.setVelocityPath(velocityPath);
			rp.setVelocityProperties(velocityProperties);
			rp.writeAll(targetType, targetDirectory, null, targetPackage);
		}

		/*
		 * Write composite datatypes
		 */
		DatatypeGeneratorUsingSpreadsheet dtp = context.getDatatypeGenerator();
		if (generateDatatypes && dtp != null) {
			ourLog.info("Writing Composite Datatypes...");
			dtp.setFilenamePrefix(filenamePrefix);
			dtp.setFilenameSuffix(filenameSuffix);
			dtp.setTemplate(template);
			dtp.setTemplateFile(templateFile);
			dtp.setVelocityPath(velocityPath);
			dtp.setVelocityProperties(velocityProperties);
			dtp.writeAll(targetType, targetDirectory, null, targetPackage);
		}

		/*
		 * Write valuesets
		 */
		ValueSetGenerator vsp = context.getValueSetGenerator();
		if (generateValueSets && vsp != null) {
			ourLog.info("Writing ValueSet Enums...");
			vsp.setFilenamePrefix(filenamePrefix);
			vsp.setFilenameSuffix(filenameSuffix);
			vsp.setTemplate(template);
			vsp.setTemplateFile(templateFile);
			vsp.setVelocityPath(velocityPath);
			vsp.setVelocityProperties(velocityProperties);
			vsp.writeMarkedValueSets(targetType, targetDirectory, targetPackage);
		}

		switch (targetType) {
			case SOURCE: {
				myProject.addCompileSourceRoot(targetSourceDirectory.getAbsolutePath());
				break;
			}
			case RESOURCE: {
				Resource resource = new Resource();
				resource.setDirectory(targetResourceDirectory.getAbsolutePath());
				if (targetFolder != null) {
					resource.addInclude(targetFolder + "/*");
				} else {
					resource.addInclude("*");
				}
				myProject.addResource(resource);
				break;
			}
			default:
		}
	}

	public static void main(String[] args) throws IOException, MojoFailureException, MojoExecutionException {

		// PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000,
		// TimeUnit.MILLISECONDS);
		// HttpClientBuilder builder = HttpClientBuilder.create();
		// builder.setConnectionManager(connectionManager);
		// CloseableHttpClient client = builder.build();
		//
		// HttpGet get = new HttpGet("http://fhir.healthintersections.com.au/open/metadata");
		// CloseableHttpResponse response = client.execute(get);
		//
		// String metadataString = EntityUtils.toString(response.getEntity());
		//
		// ourLog.info("Metadata String: {}", metadataString);

		// String metadataString = IOUtils.toString(new
		// FileInputStream("src/test/resources/healthintersections-metadata.xml"));
		// Conformance conformance = new FhirContext(Conformance.class).newXmlParser().parseResource(Conformance.class,
		// metadataString);

		TinderGenericMultiFileMojo mojo = new TinderGenericMultiFileMojo();
		mojo.myProject = new MavenProject();
		mojo.version = "dstu2";
		mojo.targetPackage = "ca.uhn.test";
		mojo.template = "/vm/jpa_resource_provider.vm";
		mojo.targetSourceDirectory = new File("target/generated/valuesets");
		mojo.execute();
	}

	class Generator extends AbstractGenerator {
		@Override
		protected void logDebug(String message) {
			ourLog.debug(message);
		}

		@Override
		protected void logInfo(String message) {
			ourLog.info(message);
		}
	}
}
